package com.codeduck.mp.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;

import java.util.Random;

/**
 * @author: codeduck@163.com
 * @since: 2021-06-01
 */
@Configuration
public class SnowflakeIdGenUtil {

    private final static Logger LOGGER = LoggerFactory.getLogger(SnowflakeIdGenUtil.class);

    /**
     * 雪花算法结构: 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
     * 第一位不用,接下来的41位为毫秒级时间(41位的长度可以使用69年),
     * 然后是5位数据中心id——datacenterId和5位机器id——workerId(10位的长度最多支持部署1024个节点）,
     * 最后12位是毫秒内的计数（12位的计数顺序号支持每个节点每毫秒产生4096个ID序号）,
     * 一共加起来刚好64位，为一个Long型。(转换成字符串长度为18)。
     * snowflake生成的ID整体上按照时间自增排序，并且整个分布式系统内不会产生ID碰撞（由datacenter和workerId作区分），并且效率较高。
     * 据说：snowflake每秒能够产生26万个ID。
     */

    // ---------------------------------------------------------------------------------------------------------------

    /**
     * 起始的时间戳
     */
    private final static long startStamp = 1622531877505L;

    /**
     * 每一部分占用的位数
     */
    private final long sequenceBits = 12L;    //序列号占用的位数
    private final long workerBits = 5L;       //机器标识占用的位数
    private final long datacenterBits = 5L;   //数据中心占用的位数

    /**
     * 每一部分的最大值: 因为二进制里第一个 bit 为如果是 1，那么都是负数，但是我们生成的 id 都是正数，所以第一个 bit 统一都是 0
     */
    private final long maxDataCenterId = -1L ^ (-1L << datacenterBits);  // 31 = 2^5 - 1
    private final long maxWorkId = -1L ^ (-1L << workerBits);            // 31
    private final long maxSequence = -1L ^ (-1L << sequenceBits);        // 4095

    /**
     * 每一部分向左的位移
     */
    private final long workerIdLeftShift = sequenceBits;
    private final long datacenterIdLeftShift = workerBits + sequenceBits;
    private final long timestampLeftShift = datacenterBits + workerBits + sequenceBits;

    private long datacenterId = 10L;        // 数据中心Id
    private long workerId = 20L;            // 机器Id
    private long sequence;                  //序列号
    private long lastTimestamp = -1L;       //记录产生时间毫秒数，判断是否是同1毫秒
    private static final Random RANDOM = new Random();

    public SnowflakeIdGenUtil() {
        this.sequence = RANDOM.nextInt(100);
    }

    public synchronized long nextId() {
        long timestamp = timeGen();

        // 若当前机器时间回拨，则无法生成id
        if (timestamp < lastTimestamp) {
            LOGGER.info("[SnowflakeIdUtil.nextId] clock is moving backwards. Rejecting requests until %d.", lastTimestamp);
            throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds",
                    lastTimestamp - timestamp));
        }

        // 如果上一毫秒与当前时间相等，则开始生成序列号
        if (timestamp == lastTimestamp) {
            // 序列号必须在maxSequence以内自增，若超出maxSequence，则该位运算保证sequence = 0，此时需要获取下一毫秒，然后再生成序列号
            this.sequence = (sequence + 1) & maxSequence;
            if (sequence == 0L) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            this.sequence = 0L;
        }

        this.lastTimestamp = timestamp;

        // 64 bit的二进制数字，转换成10进制就是个long型 = 8byte = 64bit
        return ((timestamp - startStamp) << timestampLeftShift)
                | (datacenterId << datacenterIdLeftShift)
                | (workerId << workerIdLeftShift)
                | sequence;

    }

    /**
     * 获取lastTimestamp的下一个毫秒时间
     *
     * @param lastTimestamp
     * @return long
     */
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();

        // 如果当前时间 <= lastTimestamp 则会循环获取下一毫秒时间
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }
}
